package org.mx.utils.rate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mx.error.UserInterfaceSystemErrorException;
import org.mx.error.UserInterfaceSystemErrorException.SystemErrors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import lombok.Getter;

/**
 * Top N比率计算类定义
 * 
 * @param <T> 实现了Number的类型参数，如Integer、Long、Float、Double等。
 * 
 * @author john.peng
 *
 */
public class TopNRate<T extends Number> {
	private static final Logger logger = LoggerFactory.getLogger(TopNRate.class);

	private @Getter int cachedNum = 60, realCachedNum = 60;
	private @Getter int periodSec = 1;
	private Map<Long, PerItem> cache = null;

	private long getTimePeriod() {
		return System.currentTimeMillis() / 1000 / periodSec;
	}

	private void initCache() {
		cache = new HashMap<>((int) (cachedNum / periodSec * 1.15));
		realCachedNum = cachedNum / periodSec;
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Initialize cache success, real cache num: %d.", realCachedNum));
		}
	}

	/**
	 * 设置缓存大小，介于[1, 1024]范围内的整数值，默认为60。
	 * 
	 * @param num 缓存大小
	 */
	public void setCachedNum(int num) {
		if (num > 0 && num <= 1024) {
			cachedNum = num;
			initCache();
		} else {
			if (logger.isErrorEnabled()) {
				logger.error(String.format("The cached num must in range [1, 1024], num is: %d.", num));
			}
			throw new UserInterfaceSystemErrorException(SystemErrors.SYSTEM_ILLEGAL_PARAM);
		}
	}
	
	/**
	 * 	清除缓存的数据
	 */
	public void clean() {
		if (cache != null && !cache.isEmpty()) {
			cache.clear();
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Clean cache success.");
		}
	}

	/**
	 * 推送一个值，用于计算比率。
	 * 
	 * @param t 值
	 */
	public void pushValue(T t) {
		if (cache == null) {
			initCache();
		}
		long key = getTimePeriod();
		PerItem pi;
		if (logger.isTraceEnabled()) {
			logger.trace(String.format("push value success, item: %d, value: %s.", key, t.toString()));
		}
		synchronized (cache) {
			if (cache.containsKey(key)) {
				pi = cache.get(key);
			} else {
				pi = new PerItem();
			}
			pi.num += 1;
			pi.subTotal += t.doubleValue();
			cache.put(key, pi);
			if (cache.size() > realCachedNum) {
				// may be clean invalid data
				cleanInvalidData();
			}
		}
	}

	/**
	 * 获取指定最近n个周期值的比率（平均值）
	 * 
	 * @param n 最近周期数
	 * @return 平均值
	 */
	public double getTopNRate(int n) {
		if (n <= 0) {
			n = cache.size();
		}
		long keyBase = getTimePeriod() - n;
		List<Long> keys = new ArrayList<>();
		keys.addAll(cache.keySet());
		keys.sort((L1, L2) -> (int) (L2 - L1));
		int num = 0;
		double total = 0.0;
		for (long key : keys) {
			if (key <= keyBase) {
				break;
			}
			PerItem pi = cache.get(key);
			num += pi.num;
			total += pi.subTotal;
		}
		num = Math.max(num, 1);
		return total / num;
	}

	private void cleanInvalidData() {
		List<Long> keys = new ArrayList<>();
		long keyBase = getTimePeriod() - realCachedNum;
		keys.addAll(cache.keySet());
		keys.sort((L1, L2) -> (int) (L2 - L1));
		for (int index = keys.size() - 1; index > 0; index--) {
			long key = keys.get(index);
			if (key <= keyBase) {
				cache.remove(key);
				if (logger.isTraceEnabled()) {
					logger.trace(String.format("Remove invalid key: %d.", key));
				}
			} else {
				break;
			}
		}
	}

	/**
	 * 设置单位周期的秒数，介于[1, 60]之间的整数值，默认为1。
	 * 
	 * @param sec 秒数
	 */
	public void setPeriodSec(int sec) {
		if (sec > 0 && sec <= 60) {
			periodSec = sec;
			initCache();
		} else {
			if (logger.isErrorEnabled()) {
				logger.error(String.format("The period second must in range [1, 60], second is: %d.", sec));
			}
			throw new UserInterfaceSystemErrorException(SystemErrors.SYSTEM_ILLEGAL_PARAM);
		}
	}

	/**
	 * 每单位周期内的数据项
	 */
	private class PerItem {
		private int num = 0;
		private double subTotal = 0.0;
	}
}
